package src.Players;

import src.Util.*;

public class PlayerMovement
{
    private byte[] xlateDirectionToClient = new byte[] {1, 2, 4, 7, 6, 5, 3, 0};
    private byte[] directionDeltaX = new byte[] {0, 1, 1, 1, 0,-1,-1,-1};
    private byte[] directionDeltaY = new byte[] {1, 1, 0,-1,-1,-1, 0, 1};

    public PlayerMovement()
    {
    }

    public void updatePlayerMovement(Player p, Stream str)
    {
        if(p == null || str == null)
        {
            return;
        }
        if(p.dir1 == -1)
        {
            if(p.updateRequired || p.chatTextUpdateRequired)
            {
                str.writeBits(1, 1);
                str.writeBits(2, 0);
            }
            else
            {
                str.writeBits(1, 0);
            }
        }
        else if(p.dir2 == -1)
        {
            str.writeBits(1, 1);
            str.writeBits(2, 1);
            str.writeBits(3, xlateDirectionToClient[p.dir1]);
            str.writeBits(1, (p.updateRequired || p.chatTextUpdateRequired) ? 1: 0);
        }
        else
        {
            str.writeBits(1, 1);
            str.writeBits(2, 2);
            str.writeBits(3, xlateDirectionToClient[p.dir1]);
            str.writeBits(3, xlateDirectionToClient[p.dir2]);
            str.writeBits(1, (p.updateRequired || p.chatTextUpdateRequired) ? 1: 0);
        }
    }

    private int direction(int srcX, int srcY, int destX, int destY)
    {
        int dx = destX - srcX, dy = destY - srcY;
        if(dx < 0)
        {
            if(dy < 0)
            {
                if(dx < dy)
                    return 11;
                else if(dx > dy)
                    return 9;
                else
                    return 10;
            }
            else if(dy > 0)
            {
                if(-dx < dy)
                    return 15;
                else if(-dx > dy)
                    return 13;
                else
                    return 14;
            }
            else
                return 12;
        }
        else if(dx > 0)
        {
            if(dy < 0)
            {
                if(dx < -dy)
                    return 7;
                else if(dx > -dy)
                    return 5;
                else
                    return 6;
            }
            else if(dy > 0)
            {
                if(dx < dy)
                    return 1;
                else if(dx > dy)
                    return 3;
                else
                    return 2;
            }
            else
                return 4;
        }
        else
        {
            if(dy < 0)
                return 8;
            else if(dy > 0)
                return 0;
            else
                return -1;
        }
    }

    private void resetWalkingQueue(Player p)
    {
        if(p == null || p.walkingQueueX == null || p.walkingQueueY == null)
        {
            return;
        }
        p.wQueueReadPtr = p.wQueueWritePtr = 0;
        for(int i = 0; i < p.walkingQueueSize; i++)
        {
            p.walkingQueueX[i] = p.currentX;
            p.walkingQueueY[i] = p.currentY;
        }
    }

    public void getNextPlayerMovement(Player p)
    {
        if(p == null)
        {
            return;
        }
        p.mapRegionDidChange = false;
        p.didTeleport = false;
        p.dir1 = p.dir2 = -1;
        if(p.teleportToX != -1 && p.teleportToY != -1)
        {
            p.mapRegionDidChange = true;
            if(p.mapRegionX != -1 && p.mapRegionY != -1)
            {
                int relX = p.teleportToX - p.mapRegionX * 8, relY = p.teleportToY - p.mapRegionY * 8;
                if(relX >= 2 * 8 && relX < 11 * 8 && relY >= 2 * 8 && relY < 11 * 8)
                    p.mapRegionDidChange = false;
            }
            if(p.mapRegionDidChange)
            {
                p.mapRegionX = (p.teleportToX >> 3) - 6;
                p.mapRegionY = (p.teleportToY >>3 ) - 6;
            }
            p.currentX = p.teleportToX - 8 * p.mapRegionX;
            p.currentY = p.teleportToY - 8 * p.mapRegionY;
            p.absX = p.teleportToX;
            p.absY = p.teleportToY;
            resetWalkingQueue(p);
            p.teleportToX = p.teleportToY = -1;
            p.didTeleport = true;
        }
        else
        {
            p.dir1 = getNextWalkingDirection(p);
            if(p.dir1 == -1)
            {
                return;
            }
            if(p.canWalkDelay > 0 || p.isDead)
            {
                resetWalkingQueue(p);
                return;
            }
            if(p.isRunning)
            {
                p.dir2 = getNextWalkingDirection(p);
            }
            int deltaX = 0, deltaY = 0;
            if(p.currentX < 2 * 8)
            {
                deltaX = 4 * 8;
                p.mapRegionX -= 4;
                p.mapRegionDidChange = true;
            }
            else if(p.currentX >= 11 * 8)
            {
                deltaX = -4 * 8;
                p.mapRegionX += 4;
                p.mapRegionDidChange = true;
            }
            if(p.currentY < 2 * 8)
            {
                deltaY = 4 * 8;
                p.mapRegionY -= 4;
                p.mapRegionDidChange = true;
            }
            else if(p.currentY >= 11 * 8)
            {
                deltaY = -4 * 8;
                p.mapRegionY += 4;
                p.mapRegionDidChange = true;
            }
            if(p.mapRegionDidChange)
            {
                p.currentX += deltaX;
                p.currentY += deltaY;
                for(int i = 0; i < p.walkingQueueSize; i++)
                {
                    p.walkingQueueX[i] += deltaX;
                    p.walkingQueueY[i] += deltaY;
                }
            }
        }
    }

    public void postProcessing(Player p)
    {
        if(p == null)
        {
            return;
        }
        if(p.newWalkCmdSteps > 0 && p.canWalkDelay <= 0 && !p.isDead)
        {
            int firstX = p.newWalkCmdX[0], firstY = p.newWalkCmdY[0];
            int lastDir = 0;
            boolean found = false;
            p.numTravelBackSteps = 0;
            int OldcurrentX = p.currentX; 
            int OldcurrentY = p.currentY; 
            int ptr = p.wQueueReadPtr;
            int wayPointX1 = 0, wayPointY1 = 0;
            int dir = direction(p.currentX, p.currentY, firstX, firstY);
            if(dir != -1 && (dir & 1) != 0)
            {
                do
                {
                    lastDir = dir;
                    if(--ptr < 0)
                    {
                        ptr = p.walkingQueueSize - 1;
                    }
                    p.travelBackX[p.numTravelBackSteps] = p.walkingQueueX[ptr];
                    p.travelBackY[p.numTravelBackSteps++] = p.walkingQueueY[ptr];
                    dir = direction(p.walkingQueueX[ptr], p.walkingQueueY[ptr], firstX, firstY);
                    if(lastDir != dir)
                    {
                        found = true;
                        break;
                    }
                }
                while(ptr != p.wQueueWritePtr);
            }
            else
            {
                found = true;
            }
            if(!found)
            {
		p.teleportToX = p.absX;
		p.teleportToY = p.absY;
                return;
            }
            else
            {
                p.wQueueWritePtr = p.wQueueReadPtr;
                addToWalkingQueue(p, p.currentX, p.currentY);
                if(dir != -1 && (dir & 1) != 0)
                {
                    for(int i = 0; i < p.numTravelBackSteps - 1; i++)
                    {
                        addToWalkingQueue(p, p.travelBackX[i], p.travelBackY[i]);
                    }
                    int wayPointX2 = p.travelBackX[p.numTravelBackSteps - 1], wayPointY2 = p.travelBackY[p.numTravelBackSteps - 1];
                    if(p.numTravelBackSteps == 1)
                    {
                        wayPointX1 = p.currentX;
                        wayPointY1 = p.currentY;
                    }
                    else
                    {
                        wayPointX1 = p.travelBackX[p.numTravelBackSteps - 2];
                        wayPointY1 = p.travelBackY[p.numTravelBackSteps - 2];
                    }
                    dir = direction(wayPointX1, wayPointY1, wayPointX2, wayPointY2);
                    dir >>= 1;
                    found = false;
                    int x = wayPointX1, y = wayPointY1;
                    while(x != wayPointX2 || y != wayPointY2)
                    {
                        x += directionDeltaX[dir];
                        y += directionDeltaY[dir];
                        if((direction(x, y, firstX, firstY) & 1) == 0)
                        {
                            found = true;
                            break;
                        }
                    }
                    if(found)
                    {
                        addToWalkingQueue(p, wayPointX1, wayPointY1);
                    }
                }
                else
                {
                    for(int i = 0; i < p.numTravelBackSteps; i++)
                    {
                        addToWalkingQueue(p, p.travelBackX[i], p.travelBackY[i]);
                    }
                }
                for(int i = 0; i < p.newWalkCmdSteps; i++)
                {
                    addToWalkingQueue(p, p.newWalkCmdX[i], p.newWalkCmdY[i]);
                }
            }
        }
        p.newWalkCmdSteps = 0;
    }

    private int getNextWalkingDirection(Player p)
    {
        if(p == null)
        {
            return -1;
        }
        if(p.wQueueReadPtr == p.wQueueWritePtr)
        {
            return -1;
        }
        int dir;
        do
        {
            dir = direction(p.currentX, p.currentY, p.walkingQueueX[p.wQueueReadPtr], p.walkingQueueY[p.wQueueReadPtr]);
            if(dir == -1)
            {
                p.wQueueReadPtr = (p.wQueueReadPtr + 1) % p.walkingQueueSize;
            }
            else if((dir & 1) != 0)
            {
                resetWalkingQueue(p);
                return -1;
            }
        }
        while(dir == -1 && p.wQueueReadPtr != p.wQueueWritePtr);
        if(dir == -1)
        {
            return -1;
        }
        dir >>= 1;
        p.currentX += directionDeltaX[dir];
        p.currentY += directionDeltaY[dir];
        p.absX += directionDeltaX[dir];
        p.absY += directionDeltaY[dir];
        return dir;
    }

    private void addToWalkingQueue(Player p, int x, int y)
    {
        int next = (p.wQueueWritePtr + 1) % p.walkingQueueSize;
        if(next == p.wQueueWritePtr)
        {
            return;
        }
        p.walkingQueueX[p.wQueueWritePtr] = x;
        p.walkingQueueY[p.wQueueWritePtr] = y;
        p.wQueueWritePtr = next; 
    }

    public void updateThisPlayerMovement(Player p, Stream str)
    {
        if(p == null || str == null)
        {
            return;
        }
        if(p.mapRegionDidChange)
        {
            str.createFrame(73);
            str.writeWordA(p.mapRegionX + 6);
            str.writeWord(p.mapRegionY + 6);
        }
        if(p.didTeleport)
        {
            str.createFrameVarSizeWord(81);
            str.initBitAccess();
            str.writeBits(1, 1);
            str.writeBits(2, 3);
            str.writeBits(2, p.heightLevel);
            str.writeBits(1, 1);
            str.writeBits(1, (p.updateRequired) ? 1 : 0);
            str.writeBits(7, p.currentY);
            str.writeBits(7, p.currentX);
            return;
        }
        if(p.dir1 == -1)
        {
            str.createFrameVarSizeWord(81);
            str.initBitAccess();
            if(p.updateRequired)
            {
                str.writeBits(1, 1);
                str.writeBits(2, 0);
            }
            else
            {
                str.writeBits(1, 0);
            }
        }
        else
        {
            str.createFrameVarSizeWord(81);
            str.initBitAccess();
            str.writeBits(1, 1);
            if(p.dir2 == -1)
            {
                str.writeBits(2, 1);
                str.writeBits(3, xlateDirectionToClient[p.dir1]);
                str.writeBits(1, p.updateRequired ? 1 : 0);
            }
            else
            {
                str.writeBits(2, 2);
                str.writeBits(3, xlateDirectionToClient[p.dir1]);
                str.writeBits(3, xlateDirectionToClient[p.dir2]);
                str.writeBits(1, p.updateRequired ? 1 : 0);
            }
        }
    }
}